package com.iteaj.util.http;

import com.iteaj.util.AssertUtils;
import com.iteaj.util.CommonUtils;
import com.iteaj.util.Const;
import com.iteaj.util.core.UtilsType;
import com.iteaj.util.http.adapter.JdkHttpAdapter;
import com.iteaj.util.http.build.UrlBuilder;

import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.util.*;

/**
 * create time: 2018/3/31
 *  用来构建一个Http请求需要的信息
 * @see #charset 指定文本内容的编码格式
 * @see #url 发起请求的Url
 * @see #query 请求的参数, 根据{@link ContentType}的不同主要有以下两种方式<br>
 *     1. {@link ContentType#UrlEncoded} 则将 参数拼接到Url后面 e.g http://www.iteaj.com?a=b
 *     2. {@link ContentType#Multipart} 则将参数放入body里面
 * @see #headers 请求头列表
 * @see #defaultHead 默认的请求头
 * @see #requestConfig 请求的一些默认参数
 * @author iteaj
 * @version 1.0
 * @since 1.7
 */
public abstract class AbstractBuilder<This extends AbstractBuilder> implements HttpHeaderBuilder{

    private String charset; //字符编码
    protected ContentType type;
    protected String url;
    protected StringBuilder query;
    protected List<BodyParam> bodys;
    protected Map<String, HttpHeader> headers;
    protected HttpRequestConfig requestConfig;
    private static byte[] EMPTY_CONTENT = new byte[0];

    private static Map<String, HttpHeader> defaultHead;
    static {
        defaultHead = new HashMap<>();
        defaultHead.put("Accept", new HttpHeader("Accept", "*/*"));
        defaultHead.put("Connection", new HttpHeader("Connection", "Keep-Alive"));
        defaultHead.put("Cache-Control", new HttpHeader("Cache-Control", "no-cache"));
        defaultHead.put("Accept-Encoding", new HttpHeader("Accept-Encoding", "gzip, deflate"));
    }

    public AbstractBuilder(String url, String charset) {
        this(url, charset, ContentType.UrlEncoded, HttpRequestConfig.getDefault());
    }

    public AbstractBuilder(String url, String charset, ContentType type) {
        this(url, charset, type, HttpRequestConfig.getDefault());
    }

    public AbstractBuilder(String url, ContentType type) {
        this(url, type.charset, type, HttpRequestConfig.getDefault());
    }

    public AbstractBuilder(String url, String charset
            , ContentType type, HttpRequestConfig requestConfig) {
        this.url = url;
        this.type = type;
        this.charset = charset;
        this.bodys = new ArrayList<>();
        this.query = new StringBuilder();
        this.requestConfig = requestConfig;
        this.headers = new HashMap<>(defaultHead);
    }

    /**
     *  属查询参数, 这些参数会拼接到Url后面<br>
     *      如果是{@link ContentType#UrlEncoded} 则拼接到Url后面 e.g: http://www.iteaj.com?a=b
     *      如果是{@link ContentType#Multipart} 则参数放入Body里面
     *  注意：不进行Url编码, 如需Url编码, 请自行用{@link java.net.URLEncoder}编码
     * @param name
     * @param value
     * @return
     */
    public This addQueryParam(String name, String value) {
        AssertUtils.isNotBlank(name, "未指定请求参数名", UtilsType.HTTP);
        if(this.query.length() == 0) {
            this.query.append(name).append('=').append(value);
        } else {
            this.query.append('&').append(name).append('=').append(value);
        }
        return (This) this;
    }

    /**
     *  直接设置url的QueryParam
     *
     * @param query eg: a=3&c=3
     * @return
     */
    public This setQueryParam(String query) {
        this.getQuery().append(query);
        return (This) this;
    }

    /**
     * 只有再post方法的时候才会追加
     * @see JdkHttpAdapter#writeUrlEncodingContent(HttpURLConnection, UrlBuilder)
     * @param name
     * @param value
     * @return
     */
    public This addBody(String name, String value) {
        bodys.add(new BodyParam(name, value, Const.UTF_8.name()));
        return (This) this;
    }

    @Override
    public This addHeader(String name, String value) {
        AssertUtils.isNotBlank(name, "未指定请求头名称", UtilsType.HTTP);
        AssertUtils.isNotBlank(value, "未指定请求头值："+name, UtilsType.HTTP);
        headers.put(name, new HttpHeader(name, value));
        return (This) this;
    }

    @Override
    public Iterator<HttpHeader> iterator() {
        return headers.values().iterator();
    }

    /**
     * 解析Url<br>
     *
     * @return
     */
    public String parseUrl() {
        int indexOf = url.indexOf("?");
        if(-1 != indexOf) { // e.g: www.iteaj.com?a=b
            return this.url + "&" + this.getQuery().toString();
        } else { // e.g: www.iteaj.com
            return this.url + '?' + this.getQuery().toString();
        }
    }

    public List<BodyParam> getBodys() {
        return bodys;
    }

    public String getUrl() {
        return url;
    }

    public StringBuilder getQuery() {
        return query;
    }

    public ContentType getType() {
        return type;
    }

    public HttpRequestConfig getRequestConfig() {
        return requestConfig;
    }

    public AbstractBuilder setRequestConfig(HttpRequestConfig requestConfig) {
        this.requestConfig = requestConfig;
        return this;
    }

    public String getCharset() {
        return charset;
    }

    protected void setCharset(String charset) {
        this.charset = charset;
    }

    protected void setType(ContentType type) {
        this.type = type;
    }

    /**
     * @see #type  Content-Type
     * @see #content 参数内容
     */
    public class BodyParam {

        private String name;
        private byte[] content;
        private String fileName;
        private ContentType type;

        public BodyParam(String name, String value) {
            this(name, value, Const.UTF_8.name(), ContentType.Plain);
        }

        public BodyParam(String name, String value, String charset) {
            this(name, value, charset, ContentType.Plain);
        }

        public BodyParam(String name, String value, String charset, ContentType type) {
            this(name, value.getBytes(Charset.forName(charset)), null, type);
        }

        public BodyParam(String name, byte[] content, String fileName, ContentType type) {
            this.type = type;
            this.name = name;
            this.content = content;
            this.fileName = fileName;
        }

        public byte[] getContent() {
            //将值转成字节数组
            if(CommonUtils.isNotEmpty(content)) {
                return content;
            } else {
                content = EMPTY_CONTENT;
                return content;
            }
        }

        public BodyParam setContent(byte[] content) {
            this.content = content;
            return this;
        }

        public ContentType getType() {
            return type;
        }

        public BodyParam setType(ContentType type) {
            this.type = type;
            return this;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getFileName() {
            return fileName;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        public String contentDisposition() {

            StringBuilder sb = new StringBuilder();
            //在这里getValue()返回的是文件名
            //即如果文件名存在, 则相当于上传文件 设置文件名
            sb.append("Content-Disposition: form-data; name=\"").append(getName()).append('"');
            if(getFileName() != null) {
                sb.append("; filename=\"").append(getFileName()).append("\"\r\n")
                        .append("Content-Type: octet-stream").append("\r\n")
                        .append("Content-Transfer-Encoding: binary\r\n");
            } else if(ContentType.OctetStream == getType()) {
                sb.append("\r\n").append("Content-Type: octet-stream").append("\r\n")
                        .append("Content-Transfer-Encoding: binary");
            } else {
                sb.append("\r\n").append("Content-Type: text/plain; charset=").append(getCharset())
                        .append("\r\n") .append("Content-Transfer-Encoding: 8bit");
            }

            return sb.toString();
        }
    }
}
